//===--- ClangDiagnosticConsumer.cpp - Handle Clang diagnostics -----------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/clangimporter/internal/ClangDiagnosticConsumer.h"
#include "polarphp/clangimporter/internal/ClangSourceBufferImporter.h"
#include "polarphp/clangimporter/internal/ImporterImpl.h"
#include "polarphp/ast/AstContext.h"
#include "polarphp/ast/DiagnosticsClangImporter.h"

#include "clang/AST/ASTContext.h"
#include "clang/Frontend/DiagnosticRenderer.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "llvm/ADT/STLExtras.h"

using namespace polar;
using namespace polar::importer;

namespace {
class ClangDiagRenderer final : public clang::DiagnosticNoteRenderer {
   const llvm::function_ref<void(clang::FullSourceLoc,
                                 clang::DiagnosticsEngine::Level,
                                 StringRef)> callback;

public:
   ClangDiagRenderer(const clang::LangOptions &langOpts,
                     clang::DiagnosticOptions *diagOpts,
                     decltype(callback) fn)
      : DiagnosticNoteRenderer(langOpts, diagOpts),
        callback(fn) {}

private:
   /// Is this a diagnostic that doesn't do the user any good to show if it
   /// is located in one of Swift's synthetic buffers? If so, returns true to
   /// suppress it.
   static bool shouldSuppressDiagInSwiftBuffers(clang::DiagOrStoredDiag info) {
      if (info.isNull())
         return false;

      unsigned ID;
      if (auto *activeDiag = info.dyn_cast<const clang::Diagnostic *>())
         ID = activeDiag->getID();
      else
         ID = info.get<const clang::StoredDiagnostic *>()->getID();
      return ID == clang::diag::note_module_import_here ||
             ID == clang::diag::err_module_not_built;
   }

   /// Returns true if \p loc is inside one of Swift's synthetic buffers.
   static bool isInSwiftBuffers(clang::FullSourceLoc loc) {
      StringRef bufName = StringRef(loc.getManager().getBufferName(loc));
      return bufName == ClangImporter::Implementation::moduleImportBufferName ||
             bufName == ClangImporter::Implementation::bridgingHeaderBufferName;
   }

   void emitDiagnosticMessage(clang::FullSourceLoc Loc,
                              clang::PresumedLoc PLoc,
                              clang::DiagnosticsEngine::Level Level,
                              StringRef Message,
                              ArrayRef<clang::CharSourceRange> Ranges,
                              clang::DiagOrStoredDiag Info) override {
      if (isInSwiftBuffers(Loc)) {
         // FIXME: Ideally, we'd report non-suppressed diagnostics on synthetic
         // buffers, printing their names (eg. <swift-imported-modules>:...) but
         // this risks printing _excerpts_ of those buffers to stderr too; at
         // present the synthetic buffers are "large blocks of null bytes" which
         // we definitely don't want to print out. So until we have some clever
         // way to print the name but suppress printing excerpts, we just replace
         // the Loc with an invalid one here, which suppresses both.
         Loc = clang::FullSourceLoc();
         if (shouldSuppressDiagInSwiftBuffers(Info))
            return;
      }
      callback(Loc, Level, Message);
   }

   void emitDiagnosticLoc(clang::FullSourceLoc Loc, clang::PresumedLoc PLoc,
                          clang::DiagnosticsEngine::Level Level,
                          ArrayRef<clang::CharSourceRange> Ranges) override {}

   void emitCodeContext(clang::FullSourceLoc Loc,
                        clang::DiagnosticsEngine::Level Level,
                        SmallVectorImpl<clang::CharSourceRange>& Ranges,
                        ArrayRef<clang::FixItHint> Hints) override {}

   void emitNote(clang::FullSourceLoc Loc, StringRef Message) override {
      // We get invalid note locations when trying to describe where a module
      // is imported and the actual location is in Swift. We also want to ignore
      // things like "in module X imported from <swift-imported-modules>".
      if (Loc.isInvalid() || isInSwiftBuffers(Loc))
         return;
      emitDiagnosticMessage(Loc, {}, clang::DiagnosticsEngine::Note, Message,
                            {}, {});
   }
};
} // end anonymous namespace

ClangDiagnosticConsumer::ClangDiagnosticConsumer(
   ClangImporter::Implementation &impl,
   clang::DiagnosticOptions &clangDiagOptions,
   bool dumpToStderr)
   : TextDiagnosticPrinter(llvm::errs(), &clangDiagOptions),
     ImporterImpl(impl), DumpToStderr(dumpToStderr) {}

void ClangDiagnosticConsumer::HandleDiagnostic(
   clang::DiagnosticsEngine::Level clangDiagLevel,
   const clang::Diagnostic &clangDiag) {
   // Handle the module-not-found diagnostic specially if it's a top-level module
   // we're looking for.
   if (clangDiag.getID() == clang::diag::err_module_not_found &&
       CurrentImport && clangDiag.getArgStdStr(0) == CurrentImport->getName()) {
      return;
   }

   const AstContext &ctx = ImporterImpl.PolarphpContext;
   ClangSourceBufferImporter &bufferImporter =
      ImporterImpl.getBufferImporterForDiagnostics();

   if (clangDiag.getID() == clang::diag::err_module_not_built &&
       CurrentImport && clangDiag.getArgStdStr(0) == CurrentImport->getName()) {
      SourceLoc loc = DiagLoc;
      if (clangDiag.getLocation().isValid()) {
         loc = bufferImporter.resolveSourceLocation(clangDiag.getSourceManager(),
                                                    clangDiag.getLocation());
      }

      ctx.Diags.diagnose(loc, diag::clang_cannot_build_module,
                         ctx.LangOpts.EnableObjCInterop,
                         CurrentImport->getName());
      return;
   }

   // Satisfy the default implementation (bookkeeping).
   if (DumpToStderr)
      TextDiagnosticPrinter::HandleDiagnostic(clangDiagLevel, clangDiag);
   else
      DiagnosticConsumer::HandleDiagnostic(clangDiagLevel, clangDiag);

   // FIXME: Map over source ranges in the diagnostic.
   auto emitDiag =
      [&ctx, &bufferImporter](clang::FullSourceLoc clangNoteLoc,
                              clang::DiagnosticsEngine::Level clangDiagLevel,
                              StringRef message) {
         decltype(diag::error_from_clang) diagKind;
         switch (clangDiagLevel) {
            case clang::DiagnosticsEngine::Ignored:
               return;
            case clang::DiagnosticsEngine::Note:
               diagKind = diag::note_from_clang;
               break;
            case clang::DiagnosticsEngine::Remark:
               diagKind = diag::remark_from_clang;
               break;
            case clang::DiagnosticsEngine::Warning:
               diagKind = diag::warning_from_clang;
               break;
            case clang::DiagnosticsEngine::Error:
            case clang::DiagnosticsEngine::Fatal:
               // FIXME: What happens after a fatal error in the importer?
               diagKind = diag::error_from_clang;
               break;
         }

         SourceLoc noteLoc;
         if (clangNoteLoc.isValid())
            noteLoc = bufferImporter.resolveSourceLocation(clangNoteLoc.getManager(),
                                                           clangNoteLoc);
         ctx.Diags.diagnose(noteLoc, diagKind, message);
      };

   llvm::SmallString<128> message;
   clangDiag.FormatDiagnostic(message);

   if (clangDiag.getLocation().isInvalid()) {
      // Diagnostic about the compiler arguments.
      emitDiag(clang::FullSourceLoc(), clangDiagLevel, message);

   } else {
      assert(clangDiag.hasSourceManager());
      auto clangCI = ImporterImpl.getClangInstance();
      ClangDiagRenderer renderer(clangCI->getLangOpts(),
                                 &clangCI->getDiagnosticOpts(), emitDiag);
      clang::FullSourceLoc clangDiagLoc(clangDiag.getLocation(),
                                        clangDiag.getSourceManager());
      renderer.emitDiagnostic(clangDiagLoc, clangDiagLevel, message,
                              clangDiag.getRanges(), clangDiag.getFixItHints(),
                              &clangDiag);
   }
}
